package com.sakura.flux.controller;

import com.sakura.flux.entity.Product;
import com.sakura.flux.entity.User;
import com.sakura.flux.entity.Contributor;
import com.sakura.flux.feign.GitHubClient;
import com.sakura.flux.feign.ProductClient;
import com.sakura.flux.feign.UserClient;
import com.sakura.flux.result.R;
import com.sakura.flux.service.*;
import feign.Feign;
import feign.gson.GsonDecoder;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

import reactor.util.function.Tuple2;


@RequiredArgsConstructor
@RestController
@RequestMapping("/users")
public class UserController {

    private final UserService userService;
    private final UserWebClient userWebClient;
    private final UserClient userClient;

    private static final Logger log = LoggerFactory.getLogger(UserController.class);


    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Mono<User> create(@RequestBody User user) {
        return userService.createUser(user);
    }

    @GetMapping
    public Flux<User> getAllUsers() {
        return userService.getAllUsers();
    }

    @GetMapping("/{userId}")
    public Mono<ResponseEntity<User>> getUserById(@PathVariable String userId) {
        Mono<User> user = userService.findById(userId);
        return user.map(
                        ResponseEntity::ok
                )
                .defaultIfEmpty(
                        ResponseEntity.notFound().build()
                );
    }

    @PutMapping("/{userId}")
    public Mono<ResponseEntity<User>> updateUserById(@PathVariable String userId, @RequestBody User user) {
        return userService.updateUser(userId, user)
                .map(
                        ResponseEntity::ok
                )
                .defaultIfEmpty(
                        ResponseEntity.badRequest().build()
                );
    }

    @DeleteMapping("/{userId}")
    public Mono<ResponseEntity<Void>> deleteUserById(@PathVariable String userId) {
        return userService.deleteUser(userId)
                .map(
                        r -> ResponseEntity.ok().<Void>build()
                )
                .defaultIfEmpty(
                        ResponseEntity.notFound().build()
                );
    }

    @GetMapping("/search")
    public Flux<User> searchUsers(@RequestParam("name") String name) {
        return userService.fetchUsers(name);
    }

    @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<User> streamAllUsers() {
        return userService
                .getAllUsers()
                .flatMap(
                        user -> Flux.zip(
                                Flux.interval(Duration.ofSeconds(2)),
                                Flux.fromStream(Stream.generate(() -> user))
                        ).map(
                                Tuple2::getT2
                        )
                );
    }

    @GetMapping(value = "/client/id/{userId}")
    public Mono<ResponseEntity<User>> webClientById(@PathVariable String userId) {
        Mono<User> user = userWebClient.getUserById(userId);
        return user.map(
                ResponseEntity::ok
        ).defaultIfEmpty(
                ResponseEntity.notFound().build()
        );
    }

    @GetMapping(value = "/client/name/{userName}")
    public Flux<User> webClientByName(@PathVariable String userName) {
        return userWebClient.getUserByUserName(userName);
    }

    @GetMapping("/slow-service-products")
    private List<Product> getAllProducts() throws InterruptedException {
        Thread.sleep(2000L);
        // delay
        Product p1 = new Product();
        p1.name = "Fancy Smartphone";
        p1.desc = "A stylish phone you need";

        Product p2 = new Product();
        p2.name = "Cool Watch";
        p2.desc = "The only device you need";


        Product p3 = new Product();
        p3.name = "Smart TV";
        p3.desc = "Cristal clean images";
        return Arrays.asList(p1, p2, p3);
    }

    @GetMapping("/contributors")
    public List<Contributor> getContributorsByFeign() {
        log.info("Starting BLOCKING Controller!");


        GitHubClient github = Feign.builder()
                .decoder(new GsonDecoder())
                .target(GitHubClient.class, "https://api.github.com");
        List<Contributor> contributors = github.contributors("OpenFeign", "feign");
        for (Contributor contributor : contributors) {
            System.out.println(contributor.login + " (" + contributor.contributions + ")");
        }

        log.info("Exiting BLOCKING Controller!");
        return contributors;
    }

    @GetMapping("/products")
    public List<Product> getProductsBlockingByFeign() {


        return Feign.builder()
                .decoder(new GsonDecoder())
                .target(ProductClient.class, "http://localhost:9000").
                products();

    }

    @GetMapping(value = "/products-non-blocking", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<Product> getProductsNonBlockingByFlux() {
        log.info("Starting NON-BLOCKING Controller!");

        Flux<Product> productFlux = WebClient.create()
                .get()
                .uri("http://localhost:9000/users/slow-service-products")
                .retrieve()
                .bodyToFlux(Product.class);

        productFlux.subscribe(product -> log.info("response body: ===" + product.toString()));

        log.info("Exiting NON-BLOCKING Controller!");
        return productFlux;
    }

    @GetMapping("/info")
    public Object getUserInfoByFeign() {
        return userClient.userInfo();
//        return Feign.builder()
//                .decoder(new GsonDecoder())
//                .target(UserClient.class, "http://localhost:8090").
//                userInfo();

    }
}